package com.mobdev.notepad.shared.gdocs;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.TimeZone;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import android.util.Log;

public class ConnectionManager
{

	public static enum ResourceType {
		DOCUMENT, FOLDER;

	}

	public static class ResourceInfo {
		public String id;
		public Date publishedDate;
		public Date updatedDate;
		public String title;
		//content type
		public ResourceType type;

		public String toString() {
			return "type: " + type + ", id: " + id + "\n" + "publishedDate: " + publishedDate + "\n" + "updatedDate: " + updatedDate + "\n" + "title: '" + title + "'\n";
		}
	}

	private HttpClient httpclient = new DefaultHttpClient();
	private boolean debugMode = false;


	private SimpleDateFormat sdf = new java.text.SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'");

	{
		sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
	}


    /**
     * Get all folders from google docs account.
     * @param authToken
     * @return
     * @throws Exception
     */
    public ArrayList<ResourceInfo> getAllFolders(String authToken) throws Exception
    {
		HttpGet listDocsGet = new HttpGet("https://docs.google.com/feeds/documents/private/full/-/folder?showfolders=true");
		listDocsGet.addHeader("Authorization", "GoogleLogin auth=" + authToken);
		listDocsGet.addHeader("GData-Version", "2.0");
		HttpResponse docsResponse = httpclient.execute(listDocsGet);
		return parseEntries(docsResponse.getEntity().getContent());
    }




    /**
     * Returns folderID for a given folder name. Beware! This may lead to problems in
     * case of folders with the same name.
     * @param authToken
     * @param folderName
     * @return
     * @throws Exception
     */
    public String getFolderID(String authToken, String folderName) throws Exception
    {
    	ArrayList<ResourceInfo> resources = getAllFolders(authToken);
    	String folderID = null;
    	for(ResourceInfo ri : resources)
    	{
    		if(ri.title.equals(folderName))
    		{
    			folderID = ri.id;
    			folderID = folderID.split("%3A")[1];
    			break;
    		}
    	}

    	return folderID;
    }



    /**
     * Get all documents in a specific folder. Folder is specified by name. Beware! This may
     * lead to ambiguity problem, if there are multiple folders with the same name.
     * @param authToken
     * @param folderID Folder identifier
     * @return
     * @throws Exception
     */
	public ArrayList<ResourceInfo> getAllDocumentsInFolderByID(String authToken, String folderID) throws Exception
    {
    	HttpGet listDocsGet = new HttpGet("https://docs.google.com/feeds/folders/private/full/folder%3A" + folderID);
		listDocsGet.addHeader("Authorization", "GoogleLogin auth=" + authToken);
		listDocsGet.addHeader("GData-Version", "2.0");
		HttpResponse docsResponse = httpclient.execute(listDocsGet);
		return parseEntries(docsResponse.getEntity().getContent());
    }

    /**
     * Returns a list of strings (each of which is a line from a document), or null if a document
     * was not found.
     */
    public String getDocumentTextByID(String authToken, String documentID) throws Exception
    {
    	HttpGet documentGet = new HttpGet("https://docs.google.com/feeds/download/documents/Export?docID=" + documentID + "&exportFormat=txt");
    	documentGet.addHeader("Authorization", "GoogleLogin auth=" + authToken);
		HttpResponse docsResponse = httpclient.execute(documentGet);

		BufferedReader reader = new BufferedReader(new InputStreamReader(docsResponse.getEntity().getContent()));
		StringBuffer sb = new StringBuffer();
		String line = null;
    	while ((line = reader.readLine()) != null)
    	{
    		sb.append(line + "\n");
    	}

    	String result = sb.toString();
    	result = result.replace("\n\n", "\n");

    	if(debugMode)
    	{
    		Log.v("", "Document contents:\n");
    		Log.v("", result);
    	}

    	return result;
    }



    /**
     * Create a new folder.
     * @param folderName New folder's name.
     * @return Returns the folder id
     */
    public String createNewFolder(String authToken, String folderName) throws Exception
    {
    	HttpPost httpPostReq = new HttpPost("https://docs.google.com/feeds/documents/private/full");
    	httpPostReq.addHeader("Authorization", "GoogleLogin auth=" + authToken);
    	httpPostReq.addHeader("GData-Version", "2.0");
    	httpPostReq.addHeader("Content-Type", "application/atom+xml");

    	String entity =
    			"<?xml version=\'1.0\' encoding=\'UTF-8\'?>" +
    			"<atom:entry xmlns:atom=\"http://www.w3.org/2005/Atom\">" +
    				"<atom:category scheme=\"http://schemas.google.com/g/2005#kind\" term=\"http://schemas.google.com/docs/2007#folder\" label=\"folder\"/>" +
    				"<atom:title>" + folderName + "</atom:title>" +
    			"</atom:entry>";
    	httpPostReq.setEntity(new StringEntity(entity, "UTF-8"));

    	HttpResponse response = httpclient.execute(httpPostReq);

    	Header locationHeader = response.getHeaders("Location")[0];
  		String fullDirectoryID = locationHeader.getValue();
  		String directoryID = fullDirectoryID.split("%3A")[1];
  		directoryID = directoryID.split("/")[0];

    	int status = response.getStatusLine().getStatusCode();
    	if(status != 201)
    		throw new IOException("Failed to create folder \"" + folderName + "\" on the server.");

    	return directoryID;
    }



    /**
     * Create a new document, and upload to a specified folder. Please note, that folder should already exist.
     * @param authToken
     * @param folderID Identifier of the folder, to which new document is being uploaded.
     * @param documentName New document's name.
     * @param documentText New document's text.
     * @return document id
     * @throws Exception
     */
    public String uploadNewDocumentToFolderByID(String authToken, String folderID, String documentName, String documentText) throws Exception
    {
    	HttpPost httpPostReq = new HttpPost("https://docs.google.com/feeds/folders/private/full/folder%3A" + folderID);
    	httpPostReq.addHeader("GData-Version", "2.0");
    	httpPostReq.addHeader("Authorization", "GoogleLogin auth=" + authToken);

    	//should probably do this for all special chars; lookup 'percent encoding slug header' and RFC 3986
    	//http://code.google.com/p/gdata-java-client/source/browse/trunk/java/src/com/google/gdata/util/common/base/CharEscapers.java
    	documentName = documentName.replace(" ", "%20");
    	httpPostReq.addHeader("Slug", documentName);
    	httpPostReq.addHeader("Content-Type", "text/plain"); //html");
    	//httpPostReq.addHeader("Content-Length", ""+documentText.length());
    	httpPostReq.setEntity(new StringEntity(documentText, "UTF-8"));

    	HttpResponse response = httpclient.execute(httpPostReq);

    	ArrayList<ResourceInfo> parsedData = parseEntries(response.getEntity().getContent());
    	String documentID = parsedData.get(0).id;

    	int status = response.getStatusLine().getStatusCode();
    	if(status != 201)
    		throw new IOException("Failed to create document \"" + documentName + "\" on the server.");

    	return documentID;
    }



    /**
     * Update document's content. Depending on append parameter, this will either replace the contents, or
     * append the text to the end of the file. Please note that only plain text can be used to append.
     *
     * @param authToken
     * @param documentName Document name
     * @param documentID Document identifier
     * @param newText Text to be used for update
     * @param append If true, append to the end of the document, otherwise overwrite.
     * @return true if success, false otherwise
     * @throws Exception
     */
    public void updateDocumentContentByID(String authToken, String documentName, String documentID, String newText) throws Exception
    {

    	//update content
    	HttpPut httpPutReq = new HttpPut("https://docs.google.com/feeds/media/private/full/document%3A" + documentID); // + appendString);
    	httpPutReq.addHeader("Authorization", "GoogleLogin auth=" + authToken);
    	httpPutReq.addHeader("GData-Version", "2.0");

    	//should probably do this for all special chars; lookup 'percent encoding slug header' and RFC 3986
    	//http://code.google.com/p/gdata-java-client/source/browse/trunk/java/src/com/google/gdata/util/common/base/CharEscapers.java


    	httpPutReq.addHeader("Slug", "doc");
    	httpPutReq.addHeader("Content-Type", "multipart/related; boundary=\"END_OF_PART\"");//; charset=UTF-8");
    	httpPutReq.addHeader("If-Match", "*");
    	//docPut.addHeader("charset", "UTF-8");//encoding", "UTF-8");
    	//TODO: add encoding to other upload methods


    	final String atomXML =
    			"<?xml version=\'1.0\' encoding=\'UTF-8\'?>" +
    			"<atom:entry xmlns:atom=\"http://www.w3.org/2005/Atom\">" +
    				"<atom:category scheme=\"http://schemas.google.com/g/2005#kind\" term=\"http://schemas.google.com/docs/2007#document\" label=\"document\"/>" +
    				"<atom:title>" + documentName + "</atom:title>" +
    			"</atom:entry>";

    	String SEP = "\r\n";
    	String entity = "--END_OF_PART" + SEP +
    					"Content-Type: application/atom+xml" + SEP +
    					SEP +
    					atomXML + SEP +
    					"--END_OF_PART" + SEP +
    					"Content-Type: text/plain" + SEP +
    					SEP +
    					newText + SEP +
    					"--END_OF_PART" + SEP;


    	//based on http://blog.tacticalnuclearstrike.com/2010/01/using-multipartentity-in-android-applications/
    	httpPutReq.setEntity(new StringEntity(entity, "UTF-8"));


    	HttpResponse response = httpclient.execute(httpPutReq);
    	int status = response.getStatusLine().getStatusCode();
    	//Log.e("DEBUG", "SENT: " + entity);
    	//Log.e("DEBUG", "RESPONSE " + status + ": " + toString(response.getEntity().getContent()));
    	if(status != 200)
    		throw new IOException("Failed to upload document \"" + documentName + "\".");
    }



    /**
     * Delete document from the server. It is actually being put to "Trash" folder.
     *
     * @param authToken
     * @param documentName Name of document to delete
     * @param documentID Document identifier
     * @return true if success, false otherwise
     * @throws Exception
     */
    public void deleteDocumentByID(String authToken, String documentID) throws Exception
    {
    	HttpDelete httpDeleteReq = new HttpDelete("https://docs.google.com/feeds/documents/private/full/document%3A" + documentID);
    	httpDeleteReq.addHeader("Authorization", "GoogleLogin auth=" + authToken);
    	httpDeleteReq.addHeader("If-Match", "*");
    	HttpResponse response = httpclient.execute(httpDeleteReq);

    	int status = response.getStatusLine().getStatusCode();
    	if(status != 200)
    		throw new IOException("Failed to delete document.");
    }



    /**
     * Extract documents' info (titles, links, etc.) from the given xml.
     * All the values for each document are returned as a string separated by semicolons.
     */
    public ArrayList<ResourceInfo> parseEntries(InputStream inputStream) throws Exception
    {

    	//byte[] b = toByteArray(inputStream);
    	//System.err.println("Parsing entries: \n" + new String(b, "UTF-8").replace(">", ">\n"));

    	//DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    	//DocumentBuilder db = dbf.newDocumentBuilder();
    	//Document doc = db.parse(new ByteArrayInputStream(b));

    	DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    	DocumentBuilder db = dbf.newDocumentBuilder();
    	Document doc = db.parse(inputStream);

    	ArrayList<ResourceInfo> result = new ArrayList<ResourceInfo>();
    	NodeList entries = doc.getElementsByTagName("entry");
    	for(int i = 0; i < entries.getLength(); i++)
    	{
    		ResourceInfo ri = new ResourceInfo();

    		Node currentEntry = entries.item(i);
    		NodeList entryChildren = currentEntry.getChildNodes();
			for(int j = 0; j < entryChildren.getLength(); j++)
    		{
    			Node currentNode = entryChildren.item(j);

    			if(currentNode.getNodeName().equals("id"))
    			{
    				ri.id = currentNode.getChildNodes().item(0).getNodeValue();
    			}
    			else if(currentNode.getNodeName().equals("published"))
    			{
    				String date = currentNode.getChildNodes().item(0).getNodeValue();   //eg. 2011-06-13T15:05:37.544Z
    		    	ri.publishedDate = sdf.parse(date);
    			}
    			else if(currentNode.getNodeName().equals("updated"))
    			{
    				String date = currentNode.getChildNodes().item(0).getNodeValue();  //eg. 2011-06-13T15:05:37.544Z
    		    	ri.updatedDate = sdf.parse(date);
    			}
    			else if(currentNode.getNodeName().equals("title"))
    			{
    				ri.title = currentNode.getChildNodes().item(0).getNodeValue(); //eg. Meeting Notes
    			}
       			else if(currentNode.getNodeName().equals("gd:resourceId"))
    			{
    				String type = currentNode.getChildNodes().item(0).getNodeValue();
    				if(type.startsWith("document")) {
    					ri.type = ResourceType.DOCUMENT;
    				} else if(type.startsWith("folder")) {
    					ri.type = ResourceType.FOLDER;
    				} else {
    					ri.type = null;
    					break;
    				}
    			}
    		}


			if(debugMode)
			{
				Log.v("", "Resource: " + ri);
			}


			if(ri.type == null) {
				//unable to get this document
				continue;
			}

			result.add(ri);
    	}

    	return result;
    }


//    private static byte[] toByteArray(InputStream input)  throws IOException {
//    		ByteArrayOutputStream output = new ByteArrayOutputStream();
//    		byte[] buffer = new byte[1000];
//    		int n = 0;
//    		while (-1 != (n = input.read(buffer))) {
//    			output.write(buffer, 0, n);
//    		}
//
//    		byte[] result = output.toByteArray();
//
//    		System.err.println(new String(result, "UTF-8"));
//    		return result;
//    }

//    private static String toString(InputStream input) throws IOException {
//    	return new String(toByteArray(input), "UTF-8");
//    }
}

